package cn.learn.activiti.util;

import lombok.extern.slf4j.Slf4j;
import org.activiti.api.task.model.Task;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.util.*;

/**
 * activiti7的工具类
 *
 * @author huangyezhan
 * @version 1.0
 * @date 2021年03月11日13:08
 */
@Slf4j
public class ActivitiUtils {

    /**
     * 递归遍历获取上个任务节点
     * 子流程这种情况尚未做验证！！！
     *
     * @param bpmnModel     模型数据
     * @param nodes         存储的上一节点
     * @param currentNodeId 当前节点
     */
    public static void getIncomeTaskNodesRecur(BpmnModel bpmnModel, List<FlowElement> nodes, String currentNodeId) {
        //获取当前节点
        FlowNode currFlow = (FlowNode) bpmnModel.getFlowElement(currentNodeId);
        // 用户任务、排他网关、并行网关、包含网关
        if (currFlow instanceof UserTask || currFlow instanceof ExclusiveGateway || currFlow instanceof ParallelGateway || currFlow instanceof InclusiveGateway) {
            //获取来源
            List<SequenceFlow> incomingFlows = currFlow.getIncomingFlows();
            for (SequenceFlow incomingFlow : incomingFlows) {
                FlowElement preFlowElement = incomingFlow.getSourceFlowElement();
                // 用户任务
                if (preFlowElement instanceof UserTask) {
                    nodes.add(preFlowElement);
                }
                //排他网关
                else if (preFlowElement instanceof ExclusiveGateway) {
                    getIncomeTaskNodesRecur(bpmnModel, nodes, preFlowElement.getId());
                }
                //并行网关
                else if (preFlowElement instanceof ParallelGateway) {
                    getIncomeTaskNodesRecur(bpmnModel, nodes, preFlowElement.getId());
                }
                //包含网关，包含网关还没验证过
                else if (preFlowElement instanceof InclusiveGateway) {
                    getIncomeTaskNodesRecur(bpmnModel, nodes, preFlowElement.getId());
                }
            }
        }
    }

    /**
     * 获取上一节点，不管是任务节点，还是网关节点，或者其他节点，只获取上一节点
     *
     * @param bpmnModel     模型数据
     * @param nodes         存储的上一节点
     * @param currentNodeId 当前节点
     */
    public static void getIncomeFlowNodeRecur(BpmnModel bpmnModel, List<FlowElement> nodes, String currentNodeId) {
        //获取当前节点
        FlowNode currFlow = (FlowNode) bpmnModel.getFlowElement(currentNodeId);
        //获取来源
        List<SequenceFlow> incomingFlows = currFlow.getIncomingFlows();
        for (SequenceFlow incomingFlow : incomingFlows) {
            FlowElement preFlowElement = incomingFlow.getSourceFlowElement();
            nodes.add(preFlowElement);
        }
    }

    /**
     * 判断该任务节点是否是会签
     *
     * @param userTask 任务节点
     * @return 返回结果
     */
    public static Boolean isMultiInstance(UserTask userTask) {
        MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
        return loopCharacteristics != null;
    }

    /**
     * 获取指定开始节点到指定的所有目标节点之间所有的条件线
     *
     * @param model         图形的对象
     * @param storageId     存储ID
     * @param startNodeId   开始节点ID
     * @param targetNodeIds 所有目标节点ID
     */
    public static boolean getFlows2TargetTask(BpmnModel model, Set<String> storageId, String startNodeId, Set<String> targetNodeIds) {
        Process            process       = model.getMainProcess();
        FlowNode           startNode     = (FlowNode) process.getFlowElement(startNodeId);
        List<SequenceFlow> outgoingFlows = startNode.getOutgoingFlows();
        boolean            flag          = false;
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
            String      id                = targetFlowElement.getId();
            String      expression        = outgoingFlow.getConditionExpression();
            //下一节点是：用户任务节点，是目标节点，并且线为条件线
            if ((targetFlowElement instanceof UserTask) && targetNodeIds.remove(id)) {
                if (expression != null) {
                    storageId.add(outgoingFlow.getId());
                }
                flag = true;
            }
            //网关节点
            else if (targetFlowElement instanceof Gateway) {
                boolean targetTask = getFlows2TargetTask(model, storageId, id, targetNodeIds);
                if (targetTask && expression != null) {
                    storageId.add(outgoingFlow.getId());
                }
            }
        }
        return flag;
    }

    /**
     * 获取到第一个任务节点的需要juel解析元素的id
     *
     * @param model 模型数据
     * @return 返回所有有参数元素的id集合
     */
    public static Set<String> getJuelElementIds2FirstTask(BpmnModel model) {
        Process mainProcess = model.getMainProcess();
        //获取初始节点
        FlowElement element = mainProcess.getInitialFlowElement();
        return getJuelAllElementIds2NextTask(model, element.getId());
    }

    /**
     * 获取到下一个任务节点的所有元素的id
     *
     * @param model     模型数据
     * @param currentId 当前节点的ID
     * @return 返回所有有参数元素的id集合
     */
    public static Set<String> getJuelAllElementIds2NextTask(BpmnModel model, String currentId) {
        Set<String> ids = new HashSet<>();
        //获取所有节点的关系信息
        Process     mainProcess = model.getMainProcess();
        FlowElement flowElement = mainProcess.getFlowElement(currentId);
        //获取下一元素
        if (flowElement instanceof FlowNode) {
            FlowNode flowNode = (FlowNode) flowElement;
            getNextJuelElementIds2Task(flowNode, ids);
        }
        return ids;
    }

    public static boolean isXml(String xml) {
        boolean flag = true;
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder        builder                = documentBuilderFactory.newDocumentBuilder();
            builder.parse(new InputSource(new StringReader(xml)));
        } catch (Exception e) {
            log.error("请确认文件格式：{}", e.getMessage());
            flag = false;
        }
        return flag;
    }

    /**
     * 获取下一元素的id
     *
     * @param flowNode 流节点
     * @param ids      存储元素的集合
     */
    private static void getNextJuelElementIds2Task(FlowNode flowNode, Set<String> ids) {
        List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            //当前节点的出线，如果有条件，怎加入
            String expression = outgoingFlow.getConditionExpression();
            if (StringUtils.isNotEmpty(expression)) {
                ids.add(outgoingFlow.getId());
            }
            //下一节点
            FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
            /*
             * 如果一下节点为结束节点 EndEvent， 不管
             * 是网关，则再循环去获取下一节点信息
             */
            if (targetFlowElement instanceof Gateway) {
                getNextJuelElementIds2Task((FlowNode) targetFlowElement, ids);
            }
            //下一节点为任务节点，保存id
            else if (targetFlowElement instanceof Task) {
                ids.add(targetFlowElement.getId());
            }
        }
    }

    public static List<String> getElementsByCondition(BpmnModel model) {
        List<String> list = new ArrayList<>();
        Process            process       = model.getMainProcess();
        Collection<FlowElement> flowElements = process.getFlowElements();
        for (FlowElement flowElement : flowElements) {
           if (flowElement instanceof SequenceFlow){
               SequenceFlow ele = (SequenceFlow) flowElement;
               String       expression = ele.getConditionExpression();
               if (null != expression){
                   list.add(ele.getId());
               }
           }
        }
        return list;
    }

    //====================================================================下面两个方法尚未验证====================================================================

    /**
     * <p>获取流程走过的线</p>
     *
     * @param bpmnModel                 流程对象模型
     * @param processDefinitionEntity   流程定义对象
     * @param historicActivityInstances 历史流程已经执行的节点，并已经按执行的先后顺序排序
     * @return List<String> 流程走过的线
     * @author FRH
     */
    public static List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity, List<HistoricActivityInstance> historicActivityInstances) {
        // 用以保存高亮的线flowId
        List<String> highFlows = new ArrayList<String>();
        if (historicActivityInstances == null || historicActivityInstances.size() == 0) {
            return highFlows;
        }

        // 遍历历史节点
        for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
            // 取出已执行的节点
            HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);

            // 用以保存后续开始时间相同的节点
            List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();

            // 获取下一个节点（用于连线）
            FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);
//			FlowNode sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());

            // 将后面第一个节点放在时间相同节点的集合里
            if (sameActivityImpl != null) {
                sameStartTimeNodes.add(sameActivityImpl);
            }

            // 循环后面节点，看是否有与此后继节点开始时间相同的节点，有则添加到后继节点集合
            for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
                HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
                HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
                if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) {
                    break;
                }
                // 如果第一个节点和第二个节点开始时间相同保存
                FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                sameStartTimeNodes.add(sameActivityImpl2);
            }

            // 得到节点定义的详细信息
            FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
            // 取出节点的所有出去的线，对所有的线进行遍历
            List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
            for (SequenceFlow pvmTransition : pvmTransitions) {
                // 获取节点
                FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());

                // 不是后继节点
                if (!sameStartTimeNodes.contains(pvmActivityImpl)) {
                    continue;
                }

                // 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
                highFlows.add(pvmTransition.getId());
            }
        }

        //返回高亮的线
        return highFlows;
    }

    /**
     * <p>获取下一个节点信息</p>
     *
     * @param bpmnModel                 流程模型
     * @param historicActivityInstances 历史节点
     * @param i                         当前已经遍历到的历史节点索引（找下一个节点从此节点后）
     * @param activityImpl_             当前遍历到的历史节点实例
     * @return FlowNode 下一个节点信息
     * @author FRH
     */
    private static FlowNode getNextFlowNode(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances, int i, HistoricActivityInstance activityImpl_) {
        // 保存后一个节点
        FlowNode sameActivityImpl = null;

        // 如果当前节点不是用户任务节点，则取排序的下一个节点为后续节点
        if (!"userTask".equals(activityImpl_.getActivityType())) {
            // 是最后一个节点，没有下一个节点
            if (i == historicActivityInstances.size()) {
                return sameActivityImpl;
            }
            // 不是最后一个节点，取下一个节点为后继节点
            sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到紧跟在后面的一个节点
            // 返回
            return sameActivityImpl;
        }

        // 遍历后续节点，获取当前节点后续节点
        for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
            // 后续节点
            HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k);
            // 都是userTask，且主节点与后续节点的开始时间相同，说明不是真实的后继节点
            if ("userTask".equals(activityImp2_.getActivityType()) && activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime()) {
                continue;
            }
            // 找到紧跟在后面的一个节点
            sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());
            break;
        }
        return sameActivityImpl;
    }

}
